package rsj.proxy.parse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import rsj.annotation.DynamicSelect;
import rsj.annotation.Param;
import rsj.annotation.Select;
import rsj.annotation.Update;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.temporal.Temporal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 解析sql  将占位符替换成问好
 *
 * @author yuanqinglong
 * @since 2021/5/24 9:43
 */
public class SqlParse {


    /**
     * 占位符前缀
     */
    private static final String PLACEHOLDER_PREFIX = "#{";
    /**
     * 占位符后缀
     */
    private static final String PLACEHOLDER_SUFFIX = "}";
    /**
     * 问号
     */
    public static final String QUESTION_MARK = "?";
    /**
     * 模糊查询
     */
    public static final String LIKE = "%";


    /**
     * 解析mapper接口的方法
     *
     * @param proxyInterface 代理接口
     */
    public static void parse(Class<?> proxyInterface) throws Exception {
        if (!proxyInterface.isInterface()) {
            throw new IllegalArgumentException("不支持解析非接口类型对象");
        }
        Method[] methods = proxyInterface.getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(DynamicSelect.class)) {
                continue;
            }

            String sql;
            if (method.isAnnotationPresent(Select.class)) {
                sql = method.getAnnotation(Select.class).value();
            } else if (method.isAnnotationPresent(Update.class)) {
                sql = method.getAnnotation(Update.class).value();
            } else {
                continue;
            }
            if (StringUtils.isEmpty(sql)) {
                throw new RuntimeException(method.getDeclaringClass() + "." + method.getName() + " SQL语句不存在");
            }
            // 创建SQL解析源数据
            registerSqlSource(sql, method);
        }
    }


    /**
     * 注册sql源
     *
     * @param originalSql 原始的sql
     * @param method      方法
     * @throws NoSuchMethodException 没有这样的方法异常
     */
    private static void registerSqlSource(String originalSql, Method method) throws Exception {
        SqlSource sqlSource = createSqlSource(originalSql, method);
        // 注册每个接口非动态SQL方法对应的SQL解析数据、key为方法全路径+所有参数类型
        SqlSourceFactory.registerSqlSource(method, sqlSource);
    }


    /**
     * 创建sql源
     *
     * @param originalSql 原始的sql
     * @param method      方法
     */
    public static SqlSource createSqlSource(String originalSql, Method method) throws NoSuchMethodException, ClassNotFoundException {
        List<ParameterMapping> parameterMappings = new ArrayList<>();
        String parsedSql = doParseSql(originalSql, parameterMappings);
        SqlSource sqlSource = new SqlSource(originalSql, parsedSql, parameterMappings);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int index = 0; index < method.getParameterTypes().length; index++) {
            Class<?> parameterType = method.getParameterTypes()[index];
            // 校验不支持的参数类型
            checkParameterType(parameterType, method);
            // 如果参数是简单数据类型
            if (BeanUtils.isSimpleProperty(parameterType) || Collection.class.isAssignableFrom(parameterType)) {
                // 如果使用了注解 获取注解设置的名称
                Annotation[] parameterAnnotation = parameterAnnotations[index];
                Map<? extends Class<? extends Annotation>, Annotation> annotationMap = Stream.of(parameterAnnotation).collect(Collectors.toMap(Annotation::annotationType, Function.identity()));
                Annotation annotation = annotationMap.get(Param.class);
                if (Objects.isNull(annotation) || StringUtils.isBlank(((Param) annotation).value())) {
                    throw new IllegalArgumentException(generateMethodFullClassPath(method) + parameterType.getName() + "缺少@Param注解或value值");
                }
                Param param = (Param) annotation;
                for (ParameterMapping parameterMapping : parameterMappings) {
                    if (Objects.equals(parameterMapping.getPlaceholderName(), param.value())) {
                        parameterMapping.setMethodParamIndex(index);
                        // 简单数据类型包括日期类
                        if (BeanUtils.isSimpleProperty(parameterType) || Temporal.class.isAssignableFrom(parameterType)) {
                            sqlSource.setSimpleParameters(param.value().trim(), index);
                            //ParamTypeEnum paramTypeEnum = Temporal.class.isAssignableFrom(parameterType) ? ParamTypeEnum.TEMPORAL : ParamTypeEnum.SIMPLE;
                            parameterMapping.setMethodParamTypeEnum(ParamTypeEnum.SIMPLE);
                        }

                        // 集合类型参数
                        if (Collection.class.isAssignableFrom(parameterType)) {
                            Type genericParameterType = method.getGenericParameterTypes()[index];
                            String typeName = genericParameterType.getTypeName();
                            boolean generic = typeName.contains("<") && typeName.contains(">");
                            if (!generic) {
                                throw new IllegalArgumentException(method.getName() + " 未明确泛型");
                            }
                            String genericPath = typeName.substring(typeName.indexOf("<") + 1, typeName.lastIndexOf(">"));
                            Class<?> genericType = Class.forName(genericPath);
                            if (!BeanUtils.isSimpleProperty(genericType)) {
                                throw new IllegalArgumentException(method.getName() + " 不支持复杂类型泛型");
                            }
                            parameterMapping.setMethodParamTypeEnum(ParamTypeEnum.COLLECTION);
                        }
                    }
                }
                continue;
            }
            // 如果实体类参数
            if (!BeanUtils.isSimpleProperty(parameterType)) {
                // 保存参数名称和实体类对应的get方法、通过get方法获取参数值
                sqlSource.setGetMethod(parameterType, parameterMappings, index);
            }
        }
        return sqlSource;
    }


    /**
     * 完整的类路径生成方法
     *
     * @param method 方法
     * @return {@link String}
     */
    public static String generateMethodFullClassPath(Method method) {
        return method.getDeclaringClass().getName() + "." + method.getName();
    }


    /**
     * 检查参数类型
     *
     * @param parameterType 参数类型
     * @param method        方法
     */
    private static void checkParameterType(Class<?> parameterType, Method method) {
        if (Map.class.isAssignableFrom(parameterType)) {
            throw new IllegalArgumentException(generateMethodFullClassPath(method) + " 不支持Map类型参数");
        }
        if (!method.isAnnotationPresent(DynamicSelect.class) && Array.class.isAssignableFrom(parameterType)) {
            throw new IllegalArgumentException(generateMethodFullClassPath(method) + " 非动态SQL不支持数组类型参数");
        }
    }


    /**
     * 做解析sql
     * 解析sql占位符
     *
     * @param originalSql       原始sql
     * @param parameterMappings 解析出占位符名称
     * @return {@link String}
     */
    public static String doParseSql(String originalSql, List<ParameterMapping> parameterMappings) {
        if (originalSql == null || originalSql.isEmpty()) {
            return "";
        }
        int start = originalSql.indexOf(PLACEHOLDER_PREFIX);
        if (start == -1) {
            return originalSql;
        }
        char[] src = originalSql.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder paramExpression = null;
        while (start > -1) {
            if (start > 0 && src[start - 1] == '\\') {
                builder.append(src, offset, start - offset - 1).append(PLACEHOLDER_PREFIX);
                offset = start + PLACEHOLDER_PREFIX.length();
            } else {
                if (paramExpression == null) {
                    paramExpression = new StringBuilder();
                } else {
                    paramExpression.setLength(0);
                }
                builder.append(src, offset, start - offset);
                offset = start + PLACEHOLDER_PREFIX.length();
                int end = originalSql.indexOf(PLACEHOLDER_SUFFIX, offset);
                while (end > -1) {
                    if (end > offset && src[end - 1] == '\\') {
                        paramExpression.append(src, offset, end - offset - 1).append(PLACEHOLDER_SUFFIX);
                        offset = end + PLACEHOLDER_SUFFIX.length();
                        end = originalSql.indexOf(PLACEHOLDER_SUFFIX, offset);
                        parameterMappings.add(addParameterMapping(paramExpression.toString().trim()));
                    } else {
                        paramExpression.append(src, offset, end - offset);
                        parameterMappings.add(addParameterMapping(paramExpression.toString().trim()));
                        break;
                    }
                }
                if (end == -1) {
                    builder.append(src, start, src.length - start);
                    offset = src.length;
                } else {
                    builder.append(QUESTION_MARK);
                    offset = end + PLACEHOLDER_SUFFIX.length();
                }
            }
            start = originalSql.indexOf(PLACEHOLDER_PREFIX, offset);
        }
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }


    /**
     * 添加参数映射
     *
     * @param param 参数
     * @return {@link ParameterMapping}
     */
    private static ParameterMapping addParameterMapping(String param) {
        ParameterMapping parameterMapping = new ParameterMapping();
        parameterMapping.setLike(param.contains(LIKE));
        if (parameterMapping.isLike()) {
            parameterMapping.setPlaceholderName(param.replace(LIKE, ""));
            return parameterMapping;
        }
        parameterMapping.setPlaceholderName(param);
        return parameterMapping;
    }

}
